// transmission-remote-dotnet
// https://code.google.com/p/transmission-remote-dotnet-magnet-friendly-edition/
// Copyright (C) 2009  Alan F, 2013-2014  __alex
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Net;
using TransmissionRemoteDotnet.Commmands;

namespace TransmissionRemoteDotnet
{
    public partial class UriPromptWindow : CultureForm
    {
        [System.Runtime.InteropServices.DllImport("User32.dll")]
        public static extern Int32 SetForegroundWindow(int hWnd);

        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo);

        private const byte VK_MENU = 0xA4;
        private const int KEYEVENTF_EXTENDEDKEY = 0x1;
        private const int KEYEVENTF_KEYUP = 0x2;

        private Uri currentUri;

        public UriPromptWindow()
        {
            InitializeComponent();

            // To unlock SetForegroundWindow we need to imitate Alt pressing.
            // Read more about it here: http://www.codeproject.com/Tips/76427/How-to-bring-window-to-top-with-SetForegroundWindo
            keybd_event(VK_MENU, 0, KEYEVENTF_EXTENDEDKEY | 0, 0);  // Simulate a key press
            SetForegroundWindow(Handle.ToInt32());
            keybd_event(VK_MENU, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);   // Simulate a key release
        }

        public UriPromptWindow(string uri)
        {
            InitializeComponent();
            //Uri currentUri = new Uri(uri);
            UrlTextBox.Text = uri;

            // To unlock SetForegroundWindow we need to imitate Alt pressing.
            // Read more about it here: http://www.codeproject.com/Tips/76427/How-to-bring-window-to-top-with-SetForegroundWindo
            keybd_event(VK_MENU, 0, KEYEVENTF_EXTENDEDKEY | 0, 0);  // Simulate a key press
            SetForegroundWindow(Handle.ToInt32());
            keybd_event(VK_MENU, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);   // Simulate a key release
        }

        private void button2_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            bool withoption = UseTorrentLoadDialogCheckBox.Enabled ? UseTorrentLoadDialogCheckBox.Checked : false;
            if (Program.DaemonDescriptor.Version >= 1.50 && !withoption)
            {
                if (StartTorrendPausedCheckBox.Checked)
                {
                    // this specific torrent will be started paused
                    Program.AddTorrentPausedList.Add(GetHashesFromMagnet(this.currentUri.ToString()));
                }
                    
                Program.Form.SetupAction(CommandFactory.RequestAsync(Requests.TorrentAddByUrl(this.currentUri.ToString())));
                this.Close();
            }
            else
            {
                try
                {
                    string target = Path.GetTempFileName();
                    toolStripStatusLabel.Text = OtherStrings.Downloading + "...";
                    DownloadProgressBar.Value = 0;
                    DownloadProgressBar.Visible = true;
                    OkButton.Enabled = false;
                    WebClient webClient = new TransmissionWebClient(false, false);
                    webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(webClient_DownloadProgressChanged);
                    webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(webClient_DownloadFileCompleted);
                    webClient.DownloadFileAsync(this.currentUri, target, target);
                }
                catch (Exception ex)
                {
                    HandleException(ex);
                }
            }
        }

        private void HandleException(Exception ex)
        {
            DownloadProgressBar.Visible = false;
            toolStripStatusLabel.Text = ex.Message;
            MessageBox.Show(ex.Message, OtherStrings.Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        private bool IsValidHexString(string test)
        {
            // For C-style hex notation (0xFF) you can use @"\A\b(0[xX])?[0-9a-fA-F]+\b\Z"
            return System.Text.RegularExpressions.Regex.IsMatch(test, @"\A\b[0-9a-fA-F]+\b\Z");
        }
        
        private bool IsValidBase32String(string test)
        {
            return System.Text.RegularExpressions.Regex.IsMatch(test, @"\A\b[2-7a-zA-Z]+\b\Z");
        }

        private bool ConvertHashToMagnetLinkIfNeeded(ref string uri)
        {
            const string magnetUrlPrefix = "magnet:?xt=urn:btih:";

            if (!Uri.IsWellFormedUriString(uri, UriKind.Absolute))
            {
                // it's not a filename or a valid uri. let's check if it's a hash code of magnet link
                if ((uri.Length == 40 && IsValidHexString(uri)) ||  // looks like a valid hex-encoded hash
                    (uri.Length == 32 && IsValidBase32String(uri))) // looks like a valid base32-encoded hash
                {
                    uri = magnetUrlPrefix + uri;
                    return true;    // converted
                }
            }

            return false;   // not converted
        }

        private void LeaveOnlyHash(ref string uri)
        {
            // for example, we need convert this:
            // magnet:&xl=24822941?xl=413663952&xt=urn:btih:cd7828c00f3b7e5371d0aa249188f78580d37a52&dn=True.Blood.S06E02.HDTV.x264-ASAP.%5BVTV%5D.mp4&tr=udp%3A%2F%2Ftracker.istole.it%3A80&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80
            // to this:
            // magnet:?xl=413663952&xt=urn:btih:cd7828c00f3b7e5371d0aa249188f78580d37a52

            string magnetInitial = uri.Substring(7);   // remove "magnet:" substring
            string magnetFinal="";
            string[] magnetLinkParameters = magnetInitial.Split(new char[] { '&', '?' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string param in magnetLinkParameters)
            {
                if (param.Substring(0, 2) == "xt")
                {
                    if (magnetFinal.Length == 0)
                        magnetFinal = "magnet:?" + param;
                    else
                        magnetFinal += "&" + param;
                }
            }

            uri = magnetFinal;
        }

        private string[] GetHashesFromMagnet(string magnet)
        {
            // for example, we need convert this:
            // magnet:?xt=urn:ed2k:31D6CFE0D16AE931B73C59D7E0C089C0&xl=0&dn=zero_len.fil&xt=urn:bitprint:3I42H3S6NNFQ2MSVX7XZKYAYSCX5QBYJ.LWPNACQDBZRYXW3VHJVCJ64QBZNGHOHHHZWCLNQ&xt=urn:md5:D41D8CD98F00B204E9800998ECF8427E
            // to array with this:
            // 31D6CFE0D16AE931B73C59D7E0C089C0, 3I42H3S6NNFQ2MSVX7XZKYAYSCX5QBYJ.LWPNACQDBZRYXW3VHJVCJ64QBZNGHOHHHZWCLNQ, D41D8CD98F00B204E9800998ECF8427E
            string[] hashes = new string[0];
            string magnetInitial = magnet.Substring(7);   // remove "magnet:" substring
            string[] magnetLinkParameters = magnetInitial.Split(new char[] { '&', '?' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string param in magnetLinkParameters)
            {
                if (param.Substring(0, 2) == "xt")
                {
                    int lastColonPos = param.LastIndexOf(':');
                    if (lastColonPos == -1) continue;   // error in magnet link syntax; skip this hash
                    Array.Resize(ref hashes, hashes.Length + 1);
                    hashes[hashes.Length - 1] = param.Substring(lastColonPos + 1);
                }
            }

            return hashes;
        }

        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            string uri = UrlTextBox.Text;

            if (uri.Length == 0)
            {
                toolStripStatusLabel.Text = OtherStrings.WaitingForInput + "...";
                OkButton.Enabled = false;
                return;
            }

            // dealing with a magnet link
            ConvertHashToMagnetLinkIfNeeded(ref uri);

            bool isMagnetLink = UseHashOnlyCheckBox.Enabled = StartTorrendPausedCheckBox.Enabled = 
                (uri.Length > 7 && uri.Substring(0, 7) == "magnet:");

            if (isMagnetLink && UseHashOnlyCheckBox.Checked)
                LeaveOnlyHash(ref uri);

            try
            {
                this.currentUri = new Uri(uri);
                UseTorrentLoadDialogCheckBox.Enabled = !currentUri.Scheme.Equals("magnet");
                toolStripStatusLabel.Text = OtherStrings.InputAccepted;
                OkButton.Enabled = true;
            }
            catch (Exception ex)
            {
                OkButton.Enabled = false;
                toolStripStatusLabel.Text = ex.Message;
            }
        }

        private void webClient_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                HandleException(e.Error);
                OkButton.Enabled = true;
            }
            else
            {
                bool withoption = UseTorrentLoadDialogCheckBox.Enabled ? UseTorrentLoadDialogCheckBox.Checked : false;
                if (withoption)
                {
                    TorrentLoadDialog dialog = new TorrentLoadDialog((string)e.UserState);
                    dialog.ShowDialog();
                }
                else
                    Program.Form.SetupAction(CommandFactory.RequestAsync(Requests.TorrentAddByFile((string)e.UserState, true)));
                this.Close();
            }
        }

        private void webClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            DownloadProgressBar.Value = e.ProgressPercentage;
            toolStripStatusLabel.Text = String.Format("{0} ({1}%)...", OtherStrings.Downloading, e.ProgressPercentage);
        }

        private void UriPromptWindow_Load(object sender, EventArgs e)
        {
            UseHashOnlyCheckBox.Checked = Program.Settings.UseHashOnly;

            if (UrlTextBox.Text.Length != 0) return;

            try
            {
                string cb = Clipboard.GetText();
                string uri = cb;
                
                if (ConvertHashToMagnetLinkIfNeeded(ref uri))
                    // uri has been changed, therefore clipboard contains a hash of the magnet link
                    UrlTextBox.Text = cb;
                else
                {
                    // paste clipboard text into textbox only if clipboard contains a valid uri
                    Uri uriFinal = new Uri(cb);
                    UrlTextBox.Text = uriFinal.ToString();
                }
            }
            catch
            { }
        }

        private void UseHashOnlyCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            textBox1_TextChanged(null, null);
            Program.Settings.UseHashOnly = UseHashOnlyCheckBox.Checked;
        }
    }
}
